package tv.dyndns.kishibe.client.chat;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import tv.dyndns.kishibe.client.Service;
import tv.dyndns.kishibe.client.SharedData;
import tv.dyndns.kishibe.client.StatusUpdater;
import tv.dyndns.kishibe.client.UserData;
import tv.dyndns.kishibe.client.constant.Constant;
import tv.dyndns.kishibe.client.packet.PacketChatData;
import tv.dyndns.kishibe.client.packet.PacketChatDataList;

import com.google.common.annotations.VisibleForTesting;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;

public class PanelRealtime extends Composite implements KeyDownHandler {
	private static final Logger logger = Logger.getLogger(PanelRealtime.class.getName());
	public static final int TIMER_INTERVAL = 5000;
	private static PanelRealtimeUiBinder uiBinder = GWT.create(PanelRealtimeUiBinder.class);

	interface PanelRealtimeUiBinder extends UiBinder<Widget, PanelRealtime> {
	}

	@UiField
	TextBox textBoxName;
	@UiField
	TextBox textBoxBody;
	@UiField
	Button buttonSend;
	@UiField(provided = true)
	CellListChatLog cellListChatLog;

	private int nextArrayIndex = 1;
	private final StatusUpdater<PacketChatDataList> updater = new StatusUpdater<PacketChatDataList>(
			PacketChatDataList.class.getName(), TIMER_INTERVAL) {
		@Override
		protected void request(AsyncCallback<PacketChatDataList> callback) {
			Service.Util.getInstance().receiveMessageFromChat(nextArrayIndex, callback);
		}

		@Override
		protected PacketChatDataList parse(String json) {
			return PacketChatDataList.Json.READER.read(json);
		}

		@Override
		protected void onReceived(PacketChatDataList status) {
			callbackRecieveMessage.onSuccess(status);
		}
	};
	private boolean isRecieving = false;
	private final LinkedList<PacketChatData> chatData = new LinkedList<PacketChatData>();
	private final ListDataProvider<PacketChatData> dataProvider = new ListDataProvider<PacketChatData>(
			chatData);

	public PanelRealtime() {
		cellListChatLog = new CellListChatLog(dataProvider);
		cellListChatLog.setPageSize(Constant.CHAT_MAX_RESPONSES);
		initWidget(uiBinder.createAndBindUi(this));
		textBoxName.setText(UserData.get().getPlayerName());
		textBoxBody.addKeyDownHandler(this);
	}

	private void sendMessage() {
		if (!checkContents()) {
			return;
		}

		setEnabled(false);

		final UserData record = UserData.get();
		final PacketChatData chatData = new PacketChatData();
		chatData.name = textBoxName.getText();
		chatData.body = textBoxBody.getText();
		chatData.imageFileName = record.getImageFileName();
		chatData.classLevel = record.getClassLevel();
		chatData.userCode = record.getUserCode();

		Service.Util.getInstance().sendMessageToChat(chatData, callbackSendMessage);
	}

	private final AsyncCallback<Void> callbackSendMessage = new AsyncCallback<Void>() {
		public void onSuccess(Void result) {
			textBoxBody.setText("");
			setEnabled(true);
			textBoxBody.setFocus(false);
			textBoxBody.setFocus(true);
			recieveMessage();
		}

		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "チャットメッセージの送信中にエラーが発生しました", caught);
		}
	};

	private boolean checkContents() {
		if (textBoxName.getText().trim().isEmpty()) {
			return false;
		}

		if (textBoxBody.getText().trim().isEmpty()) {
			return false;
		}

		return true;
	}

	private void recieveMessage() {
		if (isRecieving) {
			return;
		}
		isRecieving = true;

		Service.Util.getInstance().receiveMessageFromChat(nextArrayIndex, callbackRecieveMessage);
	}

	private final AsyncCallback<PacketChatDataList> callbackRecieveMessage = new AsyncCallback<PacketChatDataList>() {
		public void onSuccess(PacketChatDataList result) {
			List<PacketChatData> data = result.list;
			if (!data.isEmpty()) {
				nextArrayIndex = data.get(data.size() - 1).resId + 1;
				addMessageToPanel(result);
			}

			isRecieving = false;
		}

		public void onFailure(Throwable caught) {
			logger.log(Level.WARNING, "チャットメッセージの取得中にエラーが発生しました", caught);
		}
	};

	private void addMessageToPanel(PacketChatDataList chatDataList) {
		boolean updated = false;
		for (PacketChatData data : chatDataList.list) {
			if (data == null) {
				logger.log(
						Level.WARNING,
						"nullのチャットデータが渡されました"
								+ Arrays.deepToString(chatDataList.list
										.toArray(new PacketChatData[0])));
				continue;
			}

			final PacketChatData first = chatData.isEmpty() ? null : chatData.getFirst();
			if (!shouldShow(data, first)) {
				continue;
			}

			chatData.addFirst(data);
			updated = true;
		}

		while (chatData.size() > Constant.CHAT_MAX_RESPONSES) {
			chatData.removeLast();
		}

		if (updated) {
			dataProvider.refresh();
		}
	}

	@VisibleForTesting
	boolean shouldShow(PacketChatData data, PacketChatData last) {
		// チャット履歴が空なら表示する
		if (last == null) {
			return true;
		}

		// 最後の投稿と同じメッセージなら表示しない
		// 自分の投稿が2回表示されるバグへの対処
		if (data.body.equals(last.body) && data.userCode == last.userCode
				&& data.remoteAddress.equals(last.remoteAddress)) {
			return false;
		}

		// このユーザーから投稿されたものなら表示する
		if (data.userCode == UserData.get().getUserCode()
				&& data.remoteAddress.equals(SharedData.get().getRemoteAddress())) {
			return true;
		}

		// // 無視対象ユーザーから投稿されたものなら表示しない
		// if (SharedData.get().isLimitedUser(data.userCode, data.remoteAddress)) {
		// return false;
		// }

		// idが最後に表示されたものと同じか古ければ表示しない
		if (data.resId <= last.resId) {
			return false;
		}

		return true;
	}

	public void setEnabled(boolean enabled) {
		textBoxName.setEnabled(enabled);
		textBoxBody.setEnabled(enabled);
		buttonSend.setEnabled(enabled);
	}

	protected void onLoad() {
		super.onLoad();
		recieveMessage();
		updater.start();
	}

	@Override
	protected void onUnload() {
		updater.stop();
		super.onUnload();
	}

	@Override
	public void onKeyDown(KeyDownEvent event) {
		if (event.getSource() == textBoxBody) {
			if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
				sendMessage();
			}
		}
	}

	@UiHandler("buttonSend")
	void onButtonSend(ClickEvent e) {
		sendMessage();
	}
}
